﻿using System;
using System.Net;
using ThePresenter.Net.NetCom;
using ThePresenter.Net.PresenterCom.Actions;
using ThePresenter.Net.PresenterCom.Uids;

namespace ThePresenter.Net.PresenterCom
{

	public delegate void InformUserHandler(String message);

	class PresentationViewer : PresentationNetworkClient
	{
		#region Fields
		/// <summary>
		/// Class used for debugging informations output
		/// </summary>
		DebugClass _D;

		/// <summary>
		/// Guid of client
		/// </summary>
		Uid _Uid;

		/// <summary>
		/// Callback used when client connects to server
		/// </summary>
		private NetComClientConnectedEventHandler ConnectCallback;

		/// <summary>
		/// 
		/// </summary>
		//private Stack<PresenterAction> _ServerActions;

		#region Network fields
		/// <summary>
		/// Info about client
		/// </summary>
		ClientInfo _Info;

		/// <summary>
		/// Network client
		/// </summary>
		NetComClient _NCC;
		#endregion

		#region Interface fields
		/// <summary>
		/// Presentation used for output of incoming data
		/// </summary>
		Presentation _BoundPresentation;

		/// <summary>
		/// Slide selected on server
		/// </summary>
		Slide _ServerSelectedSlide;

		/// <summary>
		/// Should slide change when such action come from server?
		/// </summary>
		Boolean _FollowHost;

		/// <summary>
		/// Holds select action in case client is waiting for slide, server wants to select
		/// </summary>
		SelectSlideAction _DelayedSelectAction;
		#endregion
		#endregion

		#region Properties
		/// <summary>
		/// Gets a value indicating whether this instance is connected.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is connected; otherwise, <c>false</c>.
		/// </value>
		public Boolean IsConnected
		{
			get
			{
				if (this._NCC != null)
					return this._NCC.Connected;
				else
					return false;
			}
		}

		public Boolean FollowHost
		{
			get 
			{
				return this._FollowHost;
			}
			set
			{
				this._FollowHost = value;
			}
		}
		#endregion

		public event InformUserHandler OnInformUser;

		/// <summary>
		/// Initializes a new instance of the <see cref="PresentationViewer"/> class.
		/// </summary>
		/// <param name="ClientTextRepresentation">The client text representation.</param>
		public PresentationViewer(string ClientTextRepresentation)
		{
			FollowHost = true;
			_Uid = new Uid();
			_D = new DebugClass("PresentationViewer");
			_Info = new ClientInfo(_Uid, ClientTextRepresentation);
			// _ServerActions = new Stack<PresenterAction>();
		}

		#region ServerConnection Part
		/// <summary>
		/// Connects to given server and begin to display incoming drawing actions
		/// </summary>
		/// <param name="ServerEP">Target server to connect to</param>
		public void Connect(IPEndPoint ServerEP, NetComClientConnectedEventHandler Callback)
		{
			this.ConnectCallback = Callback;

			// If we dont have network client already
			if (this._NCC == null)
			{
				// Create network client
				this._NCC = new NetComClient();
				// Set incoming data handler
				this._NCC.IncomingAction += new IncomingServerActionHandler(_NCC_IncomingAction);
				// Attach handler to server disconnect event
				this._NCC.ServerDisconnected += new NetClientDisconnectedEventHandler(_NCC_ServerDisconnected);
			}

			// If we are already connected, disconnect first
			if (this.IsConnected) this.Disconnect();

			// Connect to server
			this._NCC.Connect(ServerEP, _NCC_OnConnected);
		}

		void _NCC_ServerDisconnected(Uid serverid)
		{
			System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture;

			this._BoundPresentation.SetConnectionStatus(false);
			InformUser(Properties.Resources.Client_ServerConnectionLost);
		}

		void _NCC_OnConnected(ServerInfo sender, NetComClientConnectedEventArgs e)
		{
			if (this.ConnectCallback != null) this.ConnectCallback(sender, e);

			if (e.Successful)
			{
				// Send Introduction to server
				this.SendToServer(new IntroductionAction(_Info.Uid, _Info.Name));
				this._BoundPresentation.SetConnectionStatus(true);
			}
			else
				this._BoundPresentation.SetConnectionStatus(false);
		}

		void _NCC_IncomingAction(ServerInfo server, PresenterAction action)
		{
			_D.Write("Incoming action: {0}", action.GetType().ToString());

			// this._ServerActions.Push(action);

			if (action is CanvasAction)
			{
				// TODO _ServerSelectedSlide null ref exception
				_ServerSelectedSlide.Canvas.Dispatcher.Invoke(new DrawingAction.ExecuteDelegate(((CanvasAction)action).Execute), _ServerSelectedSlide.Canvas);
				_ServerSelectedSlide.Canvas.Dispatcher.Invoke(new Action(_ServerSelectedSlide.UpdateThumbnail));
			}
			else if (action is SelectSlideAction)
			{
				Slide S = _BoundPresentation.FindSlideByUid(((SelectSlideAction)action).SelectedSlideUid);

				// if presentation does not contain requested slide
				if (S == null)
				{
					SlideRequestAction SRA = new SlideRequestAction(((SelectSlideAction)action).SelectedSlideUid);
					SendToServer(SRA);
					_DelayedSelectAction = action as SelectSlideAction;
				}
				else
				{
					if (_BoundPresentation.SelectedSlide == _ServerSelectedSlide && FollowHost)
					{
						App.Current.Dispatcher.Invoke(new Action(() => _BoundPresentation.RequestSlideSelect(S)));
					}

					this._ServerSelectedSlide = S;
				}
			}
			else if (action is SlideResponseAction)
			{
				SlideResponseAction SRA = action as SlideResponseAction;
				if (SRA.RequestedSlideAction != null)
				{
					App.Current.Dispatcher.Invoke(new Action(() => SRA.RequestedSlideAction.Execute(_BoundPresentation)));
					if (_DelayedSelectAction != null)
					{
						Slide S = _BoundPresentation.FindSlideByUid(_DelayedSelectAction.SelectedSlideUid);
						if (S != null)
						{
							this._ServerSelectedSlide = S;

							if (FollowHost)
							{
								App.Current.Dispatcher.Invoke(new Action(() => _BoundPresentation.RequestSlideSelect(this._ServerSelectedSlide)));
							}
						}
						_DelayedSelectAction = null;
					}
				}
			}
			else if (action is PresentationAction)
			{
				App.Current.Dispatcher.Invoke(new Action(() => (action as PresentationAction).Execute(_BoundPresentation)));
			}
		}

		/// <summary>
		/// Sends Action to server
		/// </summary>
		/// <param name="PresenterAction">Action to send, extended from PresenterAction</param>
		private void SendToServer(PresenterAction PresenterAction)
		{
			if (this.IsConnected)
			{
				this._NCC.SendData(PresenterAction.Serialize());
			}
			else
				InformUser(Properties.Resources.Client_ServerIsDisconnected);
		}

		/// <summary>
		/// Disconnects from currently connected server
		/// </summary>
		public void Disconnect()
		{
			if (this.IsConnected)
				this._NCC.Disconnect();
		}
		#endregion

		public override void Bind(Presentation P)
		{
			_BoundPresentation = P;
			_BoundPresentation.BindingMode = PresentationBindingMode.PresentationViewer;
			_BoundPresentation.PollAnswered += new Presentation.PollEventHandler(_BoundPresentation_PollAnswered);
		}

		void _BoundPresentation_PollAnswered(object sender, Polls.Poll poll)
		{
			AnswerPollAction A = new AnswerPollAction(poll);
			this.SendToServer(A);
		}

		void InformUser(String Message)
		{
			if (OnInformUser != null)
			{
				OnInformUser(Message);
			}		
		}

		override public void Dispose()
		{
			if (this.IsConnected)
			{
				this.Disconnect();
				this._NCC.Dispose();
			}
		}
	}
}
